package com.mingserve.common.core.utils;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * Base64编码解码工具类（自定义码表）
 *
 * @author lvwshuai
 * @date 2022-12-15
 */
public final class Base64Utils
{
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private static final byte[] S_DECODETABLE;

    /**
     * Base64 标准码表
     */
    private static final char[] S_BASE64CHAR = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
            'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
            'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
            'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
            '8', '9', '+', '/'
    };

//    private static final char S_BASE64CHAR[] = {
//            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
//            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
//            'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
//            'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
//            'y', 'z', '$', '@', '#', '&', '4', '5', '*', '7',
//            'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
//            '8', '9', '+', '/'
//    };

    static {
        S_DECODETABLE = new byte[128];
        // 初始化解码表
        Arrays.fill(S_DECODETABLE, (byte) 127);

        for (int j = 0; j < S_BASE64CHAR.length; j++)
            S_DECODETABLE[S_BASE64CHAR[j]] = (byte) j;
    }

    private Base64Utils()
    {
    }

    /**
     * 取出Base64码表值
     *
     * @param i
     * @return
     */
    public static char map64(int i)
    {
        // +:43 /:47 0:48 =:61 A:65 a:97
        if (i > 63) return '=';
//        // 标准Base64
//        byte code = (byte) (i == 62 ? '+' : i == 63 ? '/' : i < 26 ? 'A' + i : i < 52 ? 'a' + i - 26 : '0' + i - 52);
//        return (char) code;
        // 自定码表
        return S_BASE64CHAR[i];
    }

    /**
     * 解析Base64码值
     *
     * @param i
     * @return
     */
    public static byte unmap64(byte i)
    {
        // +:43 /:47 0:48 =:61 A:65 a:97
        if (i == '=') return 0;
//        // 标准Base64
//        byte index = (byte) (i == '+' ? 62 : i == '/' ? 63 : i < 'A' ? i - '0' + 52 : i < 'a' ? i - 'A' : i - 'a' + 26);
//        return (byte) index;
        // 自定码表
        return S_DECODETABLE[i];
    }

    /**
     * Base64编码（默认UTF-8）
     *
     * @param source 要编码的字符串
     * @return Base64编码后的字符串
     */
    public static String encode(String source)
    {
        return encode(source, DEFAULT_CHARSET);
    }

    /**
     * Base64编码
     *
     * @param source  要编码的字符串
     * @param charset 字符编码方式
     * @return Base64编码后的字符串
     */
    public static String encode(String source, Charset charset)
    {
        return encode(source.getBytes(charset));
    }

    /**
     * Base64编码
     *
     * @param bin 要编码的byte数组
     * @return Base64编码后的字符串
     */
    public static String encode(byte[] bin)
    {
        int i = bin.length % 3;
        int g = bin.length - i;
        StringBuilder sb = new StringBuilder();
        String fix = "";
        if (i == 1) {
            int b = (0x3f & (bin[g] << 4));
            fix += map64(0x3F & bin[g] >> 2) + "" + map64(b) + "==";
        } else if (i == 2) {
            int b = (0x03 & bin[g]) << 4 | (0xf0 & bin[g + 1]) >> 4;
            int c = (0x0f & bin[g + 1]) << 2; // !!! 最后四bits移到最高位
            fix += map64(0x3f & bin[g] >> 2) + "" + map64(b) + "" + map64(c) + '=';
        }
        for (i = 0; i < g; i += 3) {
            int a = (0xff & bin[i]) >> 2; // bigger first
            int b = (0x03 & bin[i]) << 4 | (0xF0 & bin[i + 1]) >> 4;
            int c = (0x0f & bin[i + 1]) << 2 | (0xC0 & bin[i + 2]) >> 6;
            int d = (0x3f & bin[i + 2]);
            sb.append(map64(a));
            sb.append(map64(b));
            sb.append(map64(c));
            sb.append(map64(d));
        }
        return sb + fix;
    }

    /**
     * Base64解码（默认UTF-8）
     *
     * @param source 待解码的字符串
     * @return Base64解码后的字符串
     */
    public static String decodeToStr(String source)
    {
        return decodeToStr(source, DEFAULT_CHARSET);
    }

    /**
     * Base64解码
     *
     * @param source  待解码的字符串
     * @param charset 编码方式
     * @return Base64解码后的字符串
     */
    public static String decodeToStr(String source, Charset charset)
    {
        byte[] bytes = decode(source);
        return str(bytes, charset);
    }

    /**
     * 解码字节码
     *
     * @param data 字符串
     * @param charset 字符集，如果此字段为空，则解码的结果取决于平台
     * @return 解码后的字符串
     */
    public static String str(byte[] data, Charset charset)
    {
        if (data == null)
        {
            return null;
        }

        if (null == charset)
        {
            return new String(data);
        }
        return new String(data, charset);
    }

    /**
     * Base64解码（默认UTF-8）
     *
     * @param source 待解码的字符串
     * @return Base64解码后的字符串
     */
    public static byte[] decode(String source)
    {
        return decode(source.getBytes(DEFAULT_CHARSET));
    }

    /**
     * Base64解码
     *
     * @param bytes 待解码的byte数组
     * @return Base64解码后的byte数组
     */
    public static byte[] decode(byte[] bytes)
    {
        byte[] res = new byte[bytes.length * 3 / 4];
        for (int i = 0; i < bytes.length; i += 4) {
            byte a = unmap64(bytes[i]);
            byte b = unmap64(bytes[i + 1]);
            byte c = unmap64(bytes[i + 2]);
            byte d = unmap64(bytes[i + 3]);
            res[i * 3 / 4 + 0] = (byte) (a << 2 | b >> 4);
            res[i * 3 / 4 + 1] = (byte) (b << 4 | c >> 2);
            res[i * 3 / 4 + 2] = (byte) (c << 6 | d);
        }
        int l = bytes.length;
        int pad = bytes[l - 2] == '=' ? 2 : bytes[l - 1] == '=' ? 1 : 0;
        if (pad > 0) {
            byte[] ret = new byte[res.length - pad];
            System.arraycopy(res, 0, ret, 0, res.length - pad);
            return ret;
        }
        return res;
    }

    public static void main(String[] args)
    {
        String a = "Base64的Java语言实现";
        String cp = Charset.defaultCharset().name();
        log("Default CodePage " + cp);

        String b = java.util.Base64.getEncoder().encodeToString(a.getBytes());
        String c = new String(java.util.Base64.getDecoder().decode(b));
        String d = encode(a.getBytes());
        String e = new String(decode(d));

        log("encode => " + a + " => " + b + " == " + d + "? " + (b.equals(d) ? "PASS" : "FAIL"));
        log("decode => " + c + " == " + e + "? " + (c.equals(e) ? "PASS" : "FAIL"));
    }

    public static void log(String t)
    {
        System.out.print(t + "\n");
    }
}
